﻿using System;
using System.Collections.Generic;
using System.Threading;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interception.Components;
using MugenInjection.Interface;

// ReSharper disable CheckNamespace

namespace MugenInjection.Bindings
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Represent the interceptor binding.
    /// </summary>
    internal class InterceptorBinding : IBinding
    {
        #region Nested type

        private struct CacheReference
        {
            public object Target;

            public WeakReference Proxy;
        }

        #endregion

        #region Fields

        private bool? _alwaysNewProxy;
        private readonly IBinding _binding;
        private readonly IBinding _proxyBinding;

        private readonly List<CacheReference> _interceptors = new List<CacheReference>();

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="InterceptorBinding"/> class.
        /// </summary>
        public InterceptorBinding(IBinding binding, IBinding proxyBinding)
        {
            Validate.ArgumentNotNull(binding, "binding");
            Validate.ArgumentNotNull(proxyBinding, "proxyBinding");
            _binding = binding;
            _proxyBinding = proxyBinding;
        }

        #endregion

        #region Method

        private bool TryGetFromCache(object obj, out object proxy)
        {
            for (int index = 0; index < _interceptors.Count; index++)
            {
                var cacheReference = _interceptors[index];
                var proxyObj = cacheReference.Proxy.Target;
                if (proxyObj == null)
                {
                    _interceptors.Remove(cacheReference);
                    index--;
                    continue;
                }
                if (ReferenceEquals(cacheReference.Target, obj))
                {
                    proxy = proxyObj;
                    return true;
                }
            }
            proxy = null;
            return false;
        }

        private bool IsAlwaysNew
        {
            get
            {
                if (!_alwaysNewProxy.HasValue)
                    _alwaysNewProxy = Settings.IsContain(InterceptionManagerComponent.AlwaysNewProxyKey);
                return _alwaysNewProxy.Value;
            }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            _binding.Dispose();
            _proxyBinding.Dispose();
            Monitor.Enter(_interceptors);
            _interceptors.Clear();
            Monitor.Exit(_interceptors);
        }

        #endregion

        #region Implementation of IDisposableObject

        /// <summary>
        /// Gets a value indicating whether this instance is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get { return _binding.IsDisposed; }
        }

        public event Action<IDisposableObject> Disposed
        {
            add { _binding.Disposed += value; }
            remove { _binding.Disposed -= value; }
        }

        #endregion

        #region Implementation of IBinding

        /// <summary>
        /// Gets the actions that should be called before instances are activated via the binding.
        /// </summary>
        public ICollection<Action<IBindingContext>> ActivatingActions
        {
            get { return _binding.ActivatingActions; }
        }

        /// <summary>
        /// Gets the actions that should be called after instances are activated via the binding.
        /// </summary>
        public ICollection<BindingActivatedDelegate<object>> ActivatedActions
        {
            get { return _binding.ActivatedActions; }
        }

        /// <summary>
        /// Gets the actions that should be called before instances are deactivated via the binding.
        /// </summary>
        public ICollection<Action<IBinding>> DeactivatedActions
        {
            get { return _binding.DeactivatedActions; }
        }

        /// <summary>
        /// Gets the lifecycle scope for binding.
        /// </summary>
        public IScopeLifecycle ScopeLifecycle
        {
            get { return _binding.ScopeLifecycle; }
        }

        /// <summary>
        /// Gets the priority for binding.
        /// </summary>
        public BindingPriority Priority
        {
            get { return _binding.Priority; }
        }

        /// <summary>
        /// Gets the settings for the binding.
        /// </summary>
        public ISettings Settings
        {
            get { return _binding.Settings; }
        }

        /// <summary>
        /// Gets the parameters for binding.
        /// </summary>
        public IList<IInjectionParameter> Parameters
        {
            get { return _binding.Parameters; }
        }

        /// <summary>
        /// Gets the service types.
        /// </summary>
        public IList<Type> Services
        {
            get { return _binding.Services; }
        }

        /// <summary>
        /// Determines whether the specified request can be resolved.
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <returns><c>True</c> if the specified service has been resolved; otherwise, <c>false</c>.</returns>
        public bool CanResolve(IBindingContext bindingContext)
        {
            return _binding.CanResolve(bindingContext);
        }

        /// <summary>
        /// Resolve instance for the specified <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <returns>An instance of the service.</returns>
        public object Resolve(IBindingContext bindingContext)
        {
            object resolve = _binding.Resolve(bindingContext);
            _proxyBinding.Settings[InterceptionManagerComponent.TargetKey] = new WeakReference(resolve);
            if (IsAlwaysNew)
                return _proxyBinding.Resolve(bindingContext);
            lock (_interceptors)
            {
                object obj;
                if (!TryGetFromCache(resolve, out obj))
                {
                    obj = _proxyBinding.Resolve(bindingContext);
                    _interceptors.Add(new CacheReference { Proxy = new WeakReference(obj), Target = resolve });
                }
                return obj;
            }
        }

        #endregion
    }
}